package com.fz.us.oa.web.shiro.realm;

import com.fz.us.admin.base.service.redis.ShardedRedisService;
import com.fz.us.admin.company.entity.Admin;
import com.fz.us.oa.web.shiro.Constants;
import com.fz.us.oa.web.shiro.codec.HmacSHA256Utils;
import com.fz.us.oa.web.shiro.credentials.LoginHashedCredentialsMatcher;
import com.fz.us.oa.web.shiro.service.ShiroService;
import com.fz.us.oa.web.shiro.utils.ShiroRequestUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.HashMap;
import java.util.Map;

/**
 * 需要配置realms的list，根据support token的类型来判断，一个通过即可通过
 * 或者，重写support允许多个类型在这里执行，推荐第一种方法，根据support对应多个realm
 */
public class UserRealm extends AuthorizingRealm {

    @Autowired
    private LoginHashedCredentialsMatcher loginHashedCredentialsMatcher;
    @Autowired
    private SimpleCredentialsMatcher whiteListcredentialsMatcher;

    @Autowired
    private ShiroService shiroService;
    @Autowired
    private ShardedRedisService shardedRedisService;

    /**
     * 对Token的支持类型
     */
    @Override
    public boolean supports(AuthenticationToken token) {
   //     return (token instanceof  StatelessFormToken) || (token instanceof StatelessToken) || (token instanceof AuthenticationToken);
      return (token instanceof UsernamePasswordToken) || (token instanceof StatelessBaseToken);
    }

    /**
     * 认证回调函数,验证对应的Token是否匹配.
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 登录
        if((token instanceof UsernamePasswordToken) || (token instanceof StatelessLoginToken)){
            this.setCredentialsMatcher(loginHashedCredentialsMatcher);
            return doGetStatelessLoginTokenInfo(token);
        }
        // 二维码登录
        if((token instanceof StatelessQrcodeLoginToken)){
            this.setCredentialsMatcher(whiteListcredentialsMatcher);
            return doGetStatelessQrcodeLoginTokenInfo(token);
        }
        // 无状态的审核验证过程
        if(token instanceof StatelessAuthcToken){
            this.setCredentialsMatcher(whiteListcredentialsMatcher);
            return doGetStatelessAuthcTokenInfo(token);
        }
        if(token instanceof StatelessWhiteListToken){
            this.setCredentialsMatcher(whiteListcredentialsMatcher);
            return doGetStatelessWhiteListTokenInfo(token);
        }
        return null;
    }

    /**
     * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用，这里不做Roles StringPermissions的权限允许，产生403没有权限提示
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        String aid = (String) principals.getPrimaryPrincipal();
        Admin admin = shiroService.getAdminByAdminId(aid);
        if (admin != null) {
            authorizationInfo.setRoles(shiroService.getRoleSetByAdminId(admin.getId()));
            authorizationInfo.addStringPermissions(shiroService.getDutyPermissionSetByAdminId(admin.getId()));
        }
        return authorizationInfo;
    }

    /**
     * 用户登录验证的 Token
     */
    protected AuthenticationInfo doGetStatelessLoginTokenInfo(AuthenticationToken token) throws AuthenticationException {
        // 登录账号
        String username = (String) token.getPrincipal();
        // 查找账号
        Admin admin = shiroService.getAdminByAccount(username);
        if (admin == null) {
            throw new UnknownAccountException();//错误的账号
        }
        // 检查账号状态
        checkAccountStatus(admin);

        // LoginHashedCredentialsMatcher 校验凭证
        return new SimpleAuthenticationInfo(
                admin.getId(),
                admin.getPassword(),
                ByteSource.Util.bytes(admin.getId() + admin.getSalt()),
                getName());
    }

    /**
     * 二维码登录的Token
     */
    protected AuthenticationInfo doGetStatelessQrcodeLoginTokenInfo(AuthenticationToken token) throws AuthenticationException {
        // uid
        String uid = (String) token.getPrincipal();
        if(StringUtils.isEmpty(uid)){
            throw new AuthenticationException();
        }
        // appId & appUUID & timestamp
        String strParams = shardedRedisService.get(uid);
        if(StringUtils.isEmpty(uid)){
            throw new AuthenticationException();
        }
        String[] arrayParams = strParams.split(",");
        String appId = arrayParams[0];
        String appUUID = arrayParams[1];
        String timestamp = arrayParams[2];

        Map<String,String> params = new HashMap<String, String>();
        params.put(Constants.REQUEST_HEADER_APPID, appId);
        params.put(Constants.REQUEST_HEADER_APPUUID, appUUID);
        params.put(Constants.REQUEST_HEADER_TIMESTAMP, timestamp);
        params.put(Constants.REQUEST_HEADER_UID, uid);

        String secret = shiroService.getAppSecretByAppId(appId);
        if(StringUtils.isEmpty(secret)){
            throw new AuthenticationException();
        }

        String userSecret = shiroService.getUserSecretByAdminId(uid);
        if(StringUtils.isEmpty(secret)){
            throw new AuthenticationException();
        }

        // 对param进行字典排序
        params = ShiroRequestUtil.getSortedParam(params);

        // 从服务器获取对应的 userSecret / appId appUUID timestamp uid 进行加密
        String serverUserSign = HmacSHA256Utils.digest(userSecret, params);

        // 然后进行客户端消息摘要和服务器端消息摘要的匹配
        return new SimpleAuthenticationInfo(
                uid,
                serverUserSign,
                getName());
    }

    /**
     * 白名单权限的Token
     */
    protected AuthenticationInfo doGetStatelessWhiteListTokenInfo(AuthenticationToken token) throws AuthenticationException {
        // appId
//        String appId = (String) token.getPrincipal();
//        if(StringUtils.isEmpty(appId)){
//            throw new AuthenticationException();
//        }
        // appId & appUUID & timestamp & uid
        Map<String, String> param = ((StatelessWhiteListToken) token).getParams();
        if(null == param){
            throw new AuthenticationException();
        }
        String appId = null;
        String appUUID = null;
        String timestamp = null;

        if(param.containsKey(Constants.REQUEST_HEADER_APPID)){
            appId = param.get(Constants.REQUEST_HEADER_APPID);
        }else{
            throw new AuthenticationException();
        }
        if(param.containsKey(Constants.REQUEST_HEADER_APPUUID)){
            appUUID = param.get(Constants.REQUEST_HEADER_APPUUID);
        }else{
            throw new AuthenticationException();
        }
        if(param.containsKey(Constants.REQUEST_HEADER_TIMESTAMP)){
            timestamp = param.get(Constants.REQUEST_HEADER_TIMESTAMP);
        }else{
            throw new AuthenticationException();
        }

        String secret = shiroService.getAppSecretByAppId(appId);
        if(StringUtils.isEmpty(secret)){
            throw new AuthenticationException();
        }

        // 对param进行字典排序
        param = ShiroRequestUtil.getSortedParam(param);

        // 将 appId & appUUID & timestamp 加入到redis进行校验
        if(!shiroService.compareAndSetAppTimestamp(appId, appUUID, timestamp)){
            throw new AuthenticationException();
        }

        // 从服务器获取对应的 secret / appId appUUID timestamp 进行加密
        String serverUserSign = HmacSHA256Utils.digest(secret, param);

        // 然后进行客户端消息摘要和服务器端消息摘要的匹配
        return new SimpleAuthenticationInfo(
                appId,
                serverUserSign,
                getName());
    }

    /**
     * 用户会话权限的Token，产生401没有授权错误
     */
    protected AuthenticationInfo doGetStatelessAuthcTokenInfo(AuthenticationToken token) throws AuthenticationException {
        // uid
        String uid = (String) token.getPrincipal();
        if(StringUtils.isEmpty(uid)){
            throw new AuthenticationException();
        }
        // appId & appUUID & timestamp & uid
        Map<String,String> param = ((StatelessAuthcToken) token).getParams();
        if(null == param){
            throw new AuthenticationException();
        }
        String appId = null;
        String appUUID = null;
        String timestamp = null;

        if(param.containsKey(Constants.REQUEST_HEADER_APPID)){
            appId = param.get(Constants.REQUEST_HEADER_APPID);
        }else{
            throw new AuthenticationException();
        }
        if(param.containsKey(Constants.REQUEST_HEADER_APPUUID)){
            appUUID = param.get(Constants.REQUEST_HEADER_APPUUID);
        }else{
            throw new AuthenticationException();
        }
        if(param.containsKey(Constants.REQUEST_HEADER_TIMESTAMP)){
            timestamp = param.get(Constants.REQUEST_HEADER_TIMESTAMP);
        }else{
            throw new AuthenticationException();
        }

        String secret = shiroService.getAppSecretByAppId(appId);
        if(StringUtils.isEmpty(secret)){
            throw new AuthenticationException();
        }

        String userSecret = shiroService.getUserSecretByAdminId(uid);
        if(StringUtils.isEmpty(secret)){
            throw new AuthenticationException();
        }

        // 对param进行字典排序
        param = ShiroRequestUtil.getSortedParam(param);

        // 将 appId & appUUID & timestamp 加入到redis进行校验
        if(!shiroService.compareAndSetAppTimestamp(appId, appUUID, timestamp)){
            throw new AuthenticationException();
        }

        // 从服务器获取对应的 userSecret / appId appUUID timestamp uid 进行加密
        String serverUserSign = HmacSHA256Utils.digest(userSecret, param);

        // 然后进行客户端消息摘要和服务器端消息摘要的匹配
        return new SimpleAuthenticationInfo(
                uid,
                serverUserSign,
                getName());
    }

    /**
     * 验证账号状态，禁止登录之类的
     */
    public void checkAccountStatus(Admin admin) {
        if(Boolean.FALSE.equals(admin.getIsAccountEnabled())) {
            throw new DisabledAccountException(); //账号禁用
        }
        if(Boolean.TRUE.equals(admin.getIsAccountLocked())) {
            throw new LockedAccountException(); //帐号锁定
        }
        /*
        throw new DisabledAccountException(); //账号禁用
        throw new LockedAccountException(); //帐号锁定
        throw new UnknownAccountException();//没找到帐号
        throw new ExpiredCredentialsException(); //凭证过期，需要重新登录
        */
    }

}
